package cn.felord.common.http;

import cn.felord.WeComException;
import cn.felord.common.exception.ServiceException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.web.servlet.error.AbstractErrorController;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.NoHandlerFoundException;

import java.util.List;
import java.util.Optional;

/**
 * 统一异常处理
 *
 * @author n1
 */
@Slf4j
@RestControllerAdvice(assignableTypes = {BaseController.class, AbstractErrorController.class})
public class ExceptionHandleAdvice {

    /**
     * Handle rest.
     *
     * @param request the request
     * @param e       the e
     * @return the rest
     */
    @ExceptionHandler(BindException.class)
    public Rest<?> handle(HttpServletRequest request, HttpServletResponse response, BindException e) {
        logger(request, e);
        List<ObjectError> allErrors = e.getAllErrors();
        ObjectError objectError = allErrors.get(0);
        Rest<?> rest = RestBody.failure(HttpStatus.BAD_REQUEST.value(), objectError.getDefaultMessage());
        this.addHeader(response, rest.getCode(),"bad request");
        return rest;
    }

    /**
     * Handle rest.
     *
     * @param request the request
     * @param e       the e
     * @return the rest
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Rest<?> handle(HttpServletRequest request, HttpServletResponse response, MethodArgumentNotValidException e) {
        logger(request, e);
        List<ObjectError> allErrors = e.getBindingResult().getAllErrors();
        ObjectError objectError = allErrors.get(0);
        Rest<?> rest = RestBody.failure(HttpStatus.BAD_REQUEST.value(), objectError.getDefaultMessage());
        this.addHeader(response, rest.getCode(),"bad request");
        return rest;
    }

    /**
     * Handle 404 rest.
     *
     * @param request the request
     * @param e       the e
     * @return the rest
     */
    @ExceptionHandler(NoHandlerFoundException.class)
    public Rest<?> handle(HttpServletRequest request, HttpServletResponse response, NoHandlerFoundException e) {
        String contentType = request.getHeader("Content-Type");
        log.error("统一异常处理 uri: {} content-type: {} exception: {}", e.getRequestURL(), contentType, e.toString());
        Rest<?> rest = RestBody.failure(HttpStatus.NOT_FOUND.value(), "请求路径不存在");
        this.addHeader(response, rest.getCode(),"not found");
        return rest;
    }

    /**
     * Handle rest.
     *
     * @param request the request
     * @param e       the e
     * @return the rest
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public Rest<?> handle(HttpServletRequest request, HttpServletResponse response, ConstraintViolationException e) {
        logger(request, e);
        Optional<ConstraintViolation<?>> first = e.getConstraintViolations().stream().findFirst();
        String message = first.isPresent() ? first.get().getMessage() : "";
        Rest<?> rest = RestBody.failure(HttpStatus.BAD_REQUEST.value(), message);
        this.addHeader(response, rest.getCode(),"bad request");
        return rest;
    }

    /**
     * Handle rest.
     *
     * @param request the request
     * @param e       the e
     * @return the rest
     */
    @ExceptionHandler(WeComException.class)
    public Rest<?> handle(HttpServletRequest request, HttpServletResponse response, WeComException e) {
        logger(request, e);
        Rest<?> rest = RestBody.failure(HttpStatus.BAD_REQUEST.value(), e.getMessage());
        this.addHeader(response, rest.getCode(),"bad request");
        return rest;
    }

    /**
     * Handle rest.
     *
     * @param request the request
     * @param e       the e
     * @return the rest
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public Rest<?> handle(HttpServletRequest request, HttpServletResponse response, HttpRequestMethodNotSupportedException e) {
        logger(request, e);
        String message = e.getMessage();
        Rest<?> rest = RestBody.failure(HttpStatus.METHOD_NOT_ALLOWED.value(), message);
        this.addHeader(response, rest.getCode(),"method not allowed");
        return rest;
    }

    /**
     * Handle rest.
     *
     * @param request the request
     * @param e       the e
     * @return the rest
     */
    @ExceptionHandler(AccessDeniedException.class)
    public Rest<?> handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) {
        logger(request, e);
        Rest<?> rest = RestBody.failure(HttpStatus.FORBIDDEN.value(), "权限不足，访问被拒绝");
        this.addHeader(response, rest.getCode(),"forbidden");
        return rest;
    }

    /**
     * Handle rest.
     *
     * @param request the request
     * @param e       the e
     * @return the rest
     */
    @ExceptionHandler(AuthenticationException.class)
    public Rest<?> handle(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) {
        logger(request, e);
        Rest<?> rest = RestBody.failure(HttpStatus.UNAUTHORIZED.value(), e.getMessage());
        this.addHeader(response, rest.getCode(),"unauthorized");
        return rest;
    }

    /**
     * Handle rest.
     *
     * @param request the request
     * @param e       the e
     * @return the rest
     */
    @ExceptionHandler(ServiceException.class)
    public Rest<?> handle(HttpServletRequest request, HttpServletResponse response, ServiceException e) {
        logger(request, e);
        String message = e.getMessage();
        Rest<?> rest = RestBody.failure(e.getCode(), message);
        this.addHeader(response, rest.getCode(),"service error");
        return rest;
    }

    /**
     * Handle rest.
     *
     * @param request the request
     * @param e       the e
     * @return the rest
     */
    @ExceptionHandler(Exception.class)
    public Rest<?> handle(HttpServletRequest request, HttpServletResponse response, Exception e) {
        logger(request, e);
        Rest<?> rest = RestBody.failure(500, "未知异常");
        this.addHeader(response, rest.getCode(),"unknown error");
        return rest;
    }

    private void logger(HttpServletRequest request, Exception e) {
        String contentType = request.getHeader("Content-Type");
        log.error("统一异常处理 uri: {} content-type: {} exception: {}", request.getRequestURI(), contentType, e.toString());
    }


    private void addHeader(HttpServletResponse response, int code, String msg) {
        response.addHeader("Error-Code", String.valueOf(code));
        response.addHeader("Error-Msg", msg);
    }

}
